package client;

import pojo.NetData;
import pojo.ServerMsg;

import java.io.*;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;
import java.util.*;

public class AOPClient {

    //存放要增强的类
    private final Map<String, Class<?>> proxyMap = new HashMap<>();
    //给要增强的类添加实现的地方
    private final Hashtable<Class<?>, ArrayList<ServerMsg>> servers = new Hashtable<>();

    public AOPClient() {
    }

    /**
     * 初始化时注册一个增强
     *
     * @param c    待增强的
     * @param ip   ip
     * @param port 端口
     */
    public AOPClient(Class<?> c, String ip, int port) {
        register(c, ip, port);
    }

    /**
     * 初始化时注册一个增强
     *
     * @param c         待增强的
     * @param serverMsg 服务器信息
     */
    public AOPClient(Class<?> c, ServerMsg serverMsg) {
        register(c, serverMsg);
    }

    /**
     * 注册一个增强
     *
     * @param c    待增强的
     * @param ip   ip
     * @param port 端口
     */
    public boolean register(Class<?> c, String ip, int port) {
        if (proxyMap.get(c.getName()) == null) {
            proxyMap.put(c.getName(), c);
            ArrayList<ServerMsg> serverMsgs = new ArrayList<>();
            serverMsgs.add(new ServerMsg(ip, port));
            servers.put(c, serverMsgs);
            return true;
        }
        return false;
    }

    /**
     * 注册一个增强
     *
     * @param c         待增强的
     * @param serverMsg 服务器信息
     */
    public boolean register(Class<?> c, ServerMsg serverMsg) {
        if (proxyMap.get(c.getName()) == null) {
            proxyMap.put(c.getName(), c);
            ArrayList<ServerMsg> serverMsgs = new ArrayList<>();
            serverMsgs.add(serverMsg);
            servers.put(c, serverMsgs);
            return true;
        }
        return false;
    }

    /**
     * 给指定的待增强类添加一个实现的服务器地址
     *
     * @param c    待增强的
     * @param ip   ip
     * @param port 端口
     */
    public boolean addServer(Class<?> c, String ip, int port) {
        ArrayList<ServerMsg> msgs = servers.get(c);
        if (msgs != null) {
            msgs.add(new ServerMsg(ip, port));
            return true;
        }
        return false;
    }

    /**
     * 给指定的待增强类删除服务器地址
     *
     * @param c    待增强的
     * @param ip   ip
     * @param port 端口
     */
    public boolean delServer(Class<?> c, String ip, int port) {
        ArrayList<ServerMsg> msgs = servers.get(c);
        if (msgs != null) {
            msgs.remove(new ServerMsg(ip, port));
            return true;
        }
        return false;
    }

    /**
     * 远程实现方法
     *
     * @param name 要实现的类名
     * @return 增强后的方法，如果不使用的话不会运行
     */
    public Object send(String name) {
        Class<?> aClass = proxyMap.get(name);
        if (aClass == null) {
            throw new RuntimeException("没注册这个方法");
        }
        return getProxy(aClass, name);
    }

    /**
     * 远程实现方法
     *
     * @param c 类
     * @return 类的增强
     */
    public Object send(Class<?> c) {
        String name = c.getName();
        Class<?> aClass = proxyMap.get(name);
        if (aClass == null) {
            throw new RuntimeException("没注册这个方法");
        }
        return getProxy(aClass, name);
    }

    /**
     * JDK动态代理
     *
     * @param aClass 代理的类
     * @return 增强后的方法，如果不使用的话不会运行
     */
    public Object getProxy(Class<?> aClass) {
        return getProxy(aClass, aClass.getName());
    }

    /**
     * JDK动态代理
     *
     * @param aClass 代理的类
     * @param name   代理的类在远端实现的方法的名字
     * @return 代理类实现后的值
     */
    public Object getProxy(Class<?> aClass, String name) {
        return Proxy.newProxyInstance(aClass.getClassLoader(), new Class<?>[]{aClass},
                (proxy, method, args) -> {
                    return WriteAndRead(aClass, name, method, args);
                }
        );
    }

    /**
     * 两个不同的代理共有的方法
     * 通过socket把要实现的类信息发送并将返回值接收
     */
    public Object WriteAndRead(Class<?> aClass, String name, Method method, Object[] objects) {
        //当前类所拥有的所有服务器地址
        ArrayList<ServerMsg> serverMsgs = servers.get(aClass);
        if (serverMsgs.size() == 0) {
            throw new RuntimeException("你🐎的，没有实现地址");
        }
        //返回值
        Object o = null;
        synchronized (AOPClient.class) {
            int index = 0;
            while (true) {
                ServerMsg serverMsg = serverMsgs.get(0);
                if (index == serverMsgs.size())
                    throw new RuntimeException(Thread.currentThread().getName() + ": 啧啧，全炸了，给你\uD83D\uDC34一拳");
                try {
                    //网络代理
                    Socket socket = new Socket(serverMsg.getIp(), serverMsg.getPort());
                    ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                    /*把要实现的接口信息发送出去*/
                    objectOutputStream.writeObject(new NetData(name, method.getName(), objects, method.getParameterTypes()));
                    /*接收对面发送的返回值*/
                    NetData netData = (NetData) objectInputStream.readObject();
                    if (netData.getCode() != 0) {
                        throw new RuntimeException(netData.getMsg());
                    } else {
                        o = netData.getResult();
                    }
                    /*关流，关闭外层流，会自动关闭内层，关流后socket会自动关闭*/
                    objectInputStream.close();
                    objectOutputStream.close();
                    break;
                } catch (ClassNotFoundException ignored) {
                    //这个地方的异常是对面发送了这边不知道的类会报的错
                    ignored.printStackTrace();
                    //这里会抛出新的异常，就不用搞下标了
                    serverMsgs.remove(serverMsg);
                    serverMsgs.add(serverMsg);
                    throw new RuntimeException("是不是没和对面商量好？");
                } catch (IOException e) {
                    index++;
                    serverMsgs.remove(serverMsg);
                    serverMsgs.add(serverMsg);
                    System.out.println(Thread.currentThread().getName() + ":有一个连不上了。。。" + serverMsg);
                }
            }
        }
        return o;
    }
}
